package cn.hamster3.mc.plugin.core.bukkit.util;

import cn.hamster3.mc.plugin.core.bukkit.listener.CallbackListener;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import com.google.gson.JsonObject;
import de.tr7zw.changeme.nbtapi.NBTContainer;
import de.tr7zw.changeme.nbtapi.NBTItem;
import me.clip.placeholderapi.PlaceholderAPI;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.title.Title;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@SuppressWarnings("unused")
public final class CoreBukkitUtils {
    private CoreBukkitUtils() {
    }

    /**
     * 获取玩家的头颅
     * 在1.11以上的服务端中获取头颅材质是在服务器上运行的
     * 因此建议使用异步线程调用该方法
     *
     * @param uuid 要获取的玩家
     * @return 玩家的头颅物品
     */
    @NotNull
    public static ItemStack getPlayerHead(@NotNull UUID uuid) {
        return getPlayerHead(Bukkit.getOfflinePlayer(uuid));
    }

    /**
     * 获取玩家的头颅
     * <p>
     * 在 1.11 以上的服务端建议使用异步线程调用该方法
     * <p>
     * 因为这些服务端中通过网络获取头颅材质是在服务器上运行的
     *
     * @param offlinePlayer 要获取的玩家
     * @return 玩家的头颅物品
     */
    @NotNull
    public static ItemStack getPlayerHead(@NotNull OfflinePlayer offlinePlayer) {
        ItemStack stack;
        try {
            stack = new ItemStack(Material.valueOf("PLAYER_HEAD"));
        } catch (IllegalArgumentException e) {
            //noinspection deprecation
            stack = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3);
        }
        SkullMeta meta = (SkullMeta) stack.getItemMeta();
        if (meta != null) {
            meta.setOwningPlayer(offlinePlayer);
        }
        stack.setItemMeta(meta);
        return stack;
    }

    /**
     * 获取玩家的头颅
     * 在1.11以上的服务端中获取头颅材质是在服务器上运行的
     * 因此建议使用异步线程调用该方法
     *
     * @param name 要获取的玩家
     * @return 玩家的头颅物品
     */
    @NotNull
    @SuppressWarnings("deprecation")
    public static ItemStack getPlayerHead(@NotNull String name) {
        ItemStack stack;
        try {
            stack = new ItemStack(Material.valueOf("PLAYER_HEAD"));
        } catch (IllegalArgumentException e) {
            stack = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3);
        }
        SkullMeta meta = (SkullMeta) stack.getItemMeta();
        if (meta != null) {
            meta.setOwner(name);
            stack.setItemMeta(meta);
        }
        return stack;
    }

    /**
     * 判断物品是否为空
     * 当对象为null时返回true
     * 当物品的Material为AIR时返回true
     * 当物品的数量小于1时返回true
     *
     * @param stack 物品
     * @return 是否为空
     */
    public static boolean isEmptyItemStack(@Nullable ItemStack stack) {
        return stack == null || stack.getAmount() < 1 || stack.getType() == Material.AIR;
    }

    /**
     * 判断物品是否包含某条 lore
     *
     * @param stack  要检测的物品
     * @param string 需要包含的 lore
     * @return 检测结果
     */
    public static boolean containsLore(@Nullable ItemStack stack, @NotNull String string) {
        if (stack == null) {
            return false;
        }
        ItemMeta meta = stack.getItemMeta();
        if (meta == null) {
            return false;
        }
        List<String> lore = meta.getLore();
        if (lore == null) {
            return false;
        }
        for (String s : lore) {
            if (s.contains(string)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 给予玩家一个物品, 当玩家背包满时
     * <p>
     * 将会在玩家附近生成这个物品的掉落物
     *
     * @param player 玩家
     * @param stack  物品
     */
    public static void giveItem(@NotNull HumanEntity player, @NotNull ItemStack stack) {
        if (isEmptyItemStack(stack)) {
            return;
        }
        World world = player.getWorld();
        for (ItemStack dropItem : player.getInventory().addItem(stack).values()) {
            world.dropItem(player.getLocation(), dropItem);
        }
    }

    /**
     * 获取物品名称
     * <p>
     * 当物品拥有 displayName 时，返回 displayName 的普通文本
     * <p>
     * 否则返回物品的材质名称
     *
     * @param stack 物品
     * @return 物品名称
     */
    public static String getItemName(@Nullable ItemStack stack) {
        if (isEmptyItemStack(stack)) {
            return "AIR";
        }
        ItemMeta meta = stack.getItemMeta();
        if (meta != null) {
            if (meta.hasDisplayName()) {
                return meta.getDisplayName();
            }
        }
        return stack.getType().name();
    }

    /**
     * 获取物品名称的 Component
     * <p>
     * 当物品拥有 displayName 时，返回 displayName 的普通文本
     * <p>
     * 否则返回翻译文本，以显示客户端的不同语言中物品的原版名称
     * <p>
     * 当物品为 null 时，返回 block.minecraft.air 翻译文本
     *
     * @param stack 物品
     * @return 物品名称的 Component
     */
    @NotNull
    public static Component getItemNameComponent(@Nullable ItemStack stack) {
        if (isEmptyItemStack(stack)) {
            return Component.translatable("block.minecraft.air");
        }
        ItemMeta meta = stack.getItemMeta();
        if (meta != null) {
            if (meta.hasDisplayName()) {
                return Component.text(meta.getDisplayName());
            }
        }
        Material type = stack.getType();
        if (type.isBlock()) {
            return Component.translatable("block.minecraft." + type.name().toLowerCase());
        } else {
            return Component.translatable("item.minecraft." + type.name().toLowerCase());
        }
    }

    /**
     * 从 config 中加载 DisplayMessage 实例
     *
     * @param config 配置
     * @return DisplayMessage 实例
     * @deprecated 使用 {@link #loadDisplayMessage(ConfigurationSection)}
     */
    @Deprecated
    @NotNull
    public static DisplayMessage getDisplayMessage(@NotNull ConfigurationSection config) {
        return loadDisplayMessage(config);
    }

    /**
     * 从 config 中加载 DisplayMessage 实例
     *
     * @param config 配置
     * @return DisplayMessage 实例
     */
    @NotNull
    public static DisplayMessage loadDisplayMessage(@NotNull ConfigurationSection config) {
        DisplayMessage displayMessage = new DisplayMessage();
        String message = config.getString("message");
        if (message != null) {
            displayMessage.setMessage(LegacyComponentSerializer.legacySection().deserialize(message));
        }
        String actionbar = config.getString("actionbar");
        if (actionbar != null) {
            displayMessage.setActionbar(LegacyComponentSerializer.legacySection().deserialize(actionbar));
        }
        String title = config.getString("title");
        String subtitle = config.getString("subtitle");
        if (title != null || subtitle != null) {
            displayMessage.setTitle(
                    title, subtitle,
                    config.getInt("fade-in", 10),
                    config.getInt("stay", 70),
                    config.getInt("fade-out", 20)
            );
        }
        String sound = config.getString("sound");
        if (sound != null) {
            displayMessage.setSound(sound,
                    (float) config.getDouble("volume", 1f),
                    (float) config.getDouble("pitch", 1f)
            );
        }
        return displayMessage;
    }

    /**
     * 将 DisplayMessage 实例保存到 config 中
     *
     * @param message DisplayMessage 实例
     * @return config 节点
     */
    @NotNull
    public static ConfigurationSection saveDisplayMessage(@NotNull DisplayMessage message) {
        YamlConfiguration config = new YamlConfiguration();
        if (message.getMessage() != null) {
            config.set("message", LegacyComponentSerializer.legacySection().serialize(message.getMessage()));
        }
        if (message.getActionbar() != null) {
            config.set("actionbar", LegacyComponentSerializer.legacySection().serialize(message.getActionbar()));
        }
        if (message.getTitle() != null) {
            config.set("title", LegacyComponentSerializer.legacySection().serialize(message.getTitle().title()));
            config.set("subtitle", LegacyComponentSerializer.legacySection().serialize(message.getTitle().subtitle()));
            Title.Times times = message.getTitle().times();
            if (times != null) {
                config.set("fade-in", times.fadeIn().getSeconds() / 20 + times.fadeIn().getNano() / 1000_000);
                config.set("stay", times.fadeIn().getSeconds() / 20 + times.stay().getNano() / 1000_000);
                config.set("fade-out", times.fadeIn().getSeconds() / 20 + times.fadeOut().getNano() / 1000_000);
            }
        }
        if (message.getSound() != null) {
            config.set("sound", message.getSound().name().asString());
            config.set("volume", message.getSound().volume());
            config.set("pitch", message.getSound().pitch());
        }
        return config;
    }

    @NotNull
    public static YamlConfiguration getPluginConfig(@NotNull Plugin plugin, @NotNull String filename) {
        File file = new File(plugin.getDataFolder(), filename);
        File parentFile = file.getParentFile();
        if (parentFile.mkdirs()) {
            plugin.getLogger().info("已生成插件存档文件夹");
        }
        if (!file.exists()) {
            plugin.getLogger().info("生成配置文件: " + filename);
            try (InputStream stream = plugin.getResource(filename)) {
                if (stream != null) {
                    Files.copy(stream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
                }
            } catch (IOException e) {
                throw new IllegalArgumentException("在插件 " + plugin.getName() + " 内部读取文件 " + filename + " 时发生错误");
            }
        }
        return YamlConfiguration.loadConfiguration(file);
    }

    /**
     * 将物品序列化成 nbt 字符串
     *
     * @param stack 物品实例
     * @return nbt 字符串
     */
    @NotNull
    public static String serializeItemStack(@NotNull ItemStack stack) {
        return NBTItem.convertItemtoNBT(stack).toString();
    }

    /**
     * 将 nbt 字符串反序列化为物品
     *
     * @param string nbt 字符串
     * @return 物品实例
     */
    @Nullable
    public static ItemStack deserializeItemStack(@NotNull String string) {
        return NBTItem.convertNBTtoItem(new NBTContainer(string));
    }

    /**
     * 将 nbt 字符串反序列化为物品
     *
     * @param string       nbt 字符串
     * @param defaultValue 当序列化失败时，返回 defaultValue
     * @return 物品实例
     */
    @NotNull
    public static ItemStack deserializeItemStack(@NotNull String string, @NotNull ItemStack defaultValue) {
        ItemStack stack;
        try {
            stack = deserializeItemStack(string);
        } catch (Exception ignored) {
            return defaultValue;
        }
        if (stack == null) {
            return defaultValue;
        }
        return stack;
    }

    @NotNull
    public static JsonObject serializePotionEffect(@NotNull PotionEffect effect) {
        JsonObject object = new JsonObject();
        object.addProperty("type", effect.getType().getName());
        object.addProperty("duration", effect.getDuration());
        object.addProperty("amplifier", effect.getAmplifier());
        return object;
    }

    @NotNull
    public static PotionEffect deserializePotionEffect(@NotNull JsonObject object) {
        //noinspection ConstantConditions
        return new PotionEffect(
                PotionEffectType.getByName(object.get("type").getAsString()),
                object.get("duration").getAsInt(),
                object.get("amplifier").getAsInt()
        );
    }

    public static void executeCommand(@NotNull Player player, @NotNull String command) {
        command = command.replace("%player_name%", player.getName());
        if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
            command = PlaceholderAPI.setPlaceholders(player, command);
        }
        String[] args = command.split(":", 2);
        switch (args[0]) {
            case "player": {
                Bukkit.dispatchCommand(player, args[1]);
                break;
            }
            case "console": {
                Bukkit.dispatchCommand(Bukkit.getConsoleSender(), args[1]);
                break;
            }
            case "message": {
                player.sendMessage(args[1]);
                break;
            }
            case "op": {
                boolean op = player.isOp();
                player.setOp(true);
                try {
                    Bukkit.dispatchCommand(player, args[1]);
                } finally {
                    player.setOp(op);
                }
                break;
            }
            default: {
                Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
                break;
            }
        }
    }

    @NotNull
    public static CompletableFuture<String> getPlayerChat(HumanEntity player) {
        return CallbackListener.CHATS.computeIfAbsent(player.getUniqueId(), o -> new CompletableFuture<>());
    }

    @NotNull
    public static CompletableFuture<Block> getPlayerClickedBlock(HumanEntity player) {
        return CallbackListener.BLOCKS.computeIfAbsent(player.getUniqueId(), o -> new CompletableFuture<>());
    }

    @NotNull
    public static CompletableFuture<Entity> getPlayerClickedEntity(HumanEntity player) {
        return CallbackListener.ENTITIES.computeIfAbsent(player.getUniqueId(), o -> new CompletableFuture<>());
    }
}
